#include "iConfig.hpp"

#include <glf/debugger/debugger.h>
#include <glf/debugger/tweaker.h>
#include <glf/io/fileStream.h>
#include <glf/io/memoryStream.h>
#include <stdlib.h>
#include <ctype.h>

namespace glf
{
namespace debugger
{

#if GLF_ENABLE_DEBUGGER

class XmlHandler : public XmlReader::Handler
{
public:
	XmlHandler(Tweakable* tweakable) :
		mTweakable(tweakable)
	{
		mStack.push_back(&tweakable->mBaseGroup);
	}

	virtual void BeginTag(StringMap& tagAttr)
	{
		if(tagAttr["name"] == "Tweakable" && tagAttr["value"] != "")
		{
			GLF_ASSERT(mTweakable->mClassName.size() == 0);
			mTweakable->mClassName = tagAttr["value"];
		}
		else if(tagAttr["<id>"] != "attributes" && tagAttr["<id>"] != "group")
		{
			Tweakable::Type type = mTweakable->GetType(tagAttr["<id>"]);
			std::string name = tagAttr["name"];
			std::string value = tagAttr["value"];
			std::string range = tagAttr["range"];
			std::string desc = tagAttr["desc"];

			std::map<std::string, Tweakable::Mapping>::iterator it = mStack.back()->mMappings.find(name.c_str());
			if(it != mStack.back()->mMappings.end())
			{
				if(it->second.mRange.size() == 0)
				{
					it->second.mRange = range;
				}
				if(it->second.mDesc.size() == 0)
				{
					it->second.mDesc = desc;
				}
			}

			mTweakable->SetValue(*mStack.back(), type, name, value);
		}
		else if(tagAttr["<id>"] == "group")
		{
			const char* name = tagAttr["name"].c_str();
			Tweakable::Group* childGroup = &mDummyGroup;

			for(size_t i = 0; i < mStack.back()->mSubGroups.size(); i++)
			{
				if(mStack.back()->mSubGroups[i]->mName == name)
				{
					childGroup = mStack.back()->mSubGroups[i];
					break;
				}
			}
			mStack.push_back(childGroup);
		}
	}

	virtual void EndTag(const std::string& id)
	{
		if(id == "group")
		{
			mStack.pop_back();
		}
	}

	Tweakable::Group mDummyGroup;
	std::vector<Tweakable::Group*> mStack;
	Tweakable* mTweakable;
};

#endif // GLF_ENABLE_DEBUGGER

Tweakable::Tweakable() :
	mCurrentGroup(NULL),
	mColumnVar(0),
	mPersistent(false)
{
	mCurrentGroup = &mBaseGroup;

#if GLF_ENABLE_DEBUGGER
	Tweakers::GetInstance()->RegisterTweakable(this);
#endif
}

Tweakable::~Tweakable()
{
#if GLF_ENABLE_DEBUGGER
	Tweakers::GetInstance()->UnregisterTweakable(this);
#endif
}

const char* Tweakable::GetInstanceName() const
{
	return GetClassName();
}

void Tweakable::WriteValues(std::string& xml)
{
#if GLF_ENABLE_DEBUGGER
	XmlWriter writer;
	writer.BeginTag("attributes");
	WriteGroup(mBaseGroup, writer, false);
	writer.EndTag();

	xml = writer.GetXml();
#endif
}

void Tweakable::LoadXML(const char* xmlFileName, bool setValues)
{
	FileStream stream(xmlFileName);
	if(stream.IsOpened())
	{
		LoadXML(stream, false);
	}
}

void Tweakable::LoadXML(IOStream& xmlFile, bool setValues)
{
	xmlFile.Seek(0, ios::beg);

	std::vector<char> xml;
	xml.resize(xmlFile.GetSize() + 1);
	int remaining = xmlFile.GetSize();
	int offset = 0;
	while(remaining > 0)
	{
		int count = xmlFile.Read(&xml[offset], remaining);
		remaining -= count;
		offset += count;
	}

	std::string str = &xml[0];
	SetValuesFromXML(str, setValues);
}

void Tweakable::SetValuesFromXML(const std::string& xml, bool setValues)
{
#if GLF_ENABLE_DEBUGGER
	XmlHandler handler(this);
	XmlReader reader(&handler);
	reader.Parse(xml);
#endif
}

void Tweakable::SaveXML(const char* xmlFileName)
{
	FileStream stream(xmlFileName, ios::write | ios::create);
	if(stream.IsOpened())
	{
		std::string xml;
		WriteValues(xml);
		stream.Write(xml.c_str(), xml.size());
	}
}

void Tweakable::SetPersistent()
{
	mPersistent = true;
	LoadPersistent();
}

std::string Tweakable::GetPersistentName()
{
	std::string name = GetInstanceName();
	name += ".xml";
	return name;
}

void Tweakable::LoadPersistent()
{
	GLF_ASSERT(mPersistent);

	FileStream stream(GetPersistentName().c_str(), ios::read | ios::loc_home);
	if(stream.IsOpened())
	{
		LoadXML(stream, true);
	}
}

void Tweakable::SavePersistent()
{
	GLF_ASSERT(mPersistent);

	FileStream stream(GetPersistentName().c_str(), ios::create | ios::trunc | ios::write | ios::loc_home);
	if(stream.IsOpened())
	{
		std::string xml;
		WriteValues(xml);
		stream.Write(xml.c_str(), xml.size());
	}
}

void Tweakable::OnSetValue(const std::string& name)
{
}

Tweakable::Type Tweakable::GetType(const std::string& id)
{
	if(id == "int")
		return TYPE_INT;
	if(id == "float")
		return TYPE_FLOAT;
	if(id == "string")
		return TYPE_STRING;
	if(id == "bool")
		return TYPE_BOOL;
	if(id == "color")
		return TYPE_COLOR;
	if(id == "colorf")
		return TYPE_COLORF;
	if(id == "vector2d")
		return TYPE_VECTOR2D;
	if(id == "vector3d")
		return TYPE_VECTOR3D;
	if(id == "vector4d")
		return TYPE_VECTOR4D;

	return TYPE_UNKNOWN;
}

Tweakable::Color::Color(const std::string& val)
{
	uint argb = 0;
	for(int k = val.size() - 1, order = 0; k >= 0; k--, order += 4)
	{
		char digit = tolower(val.c_str()[k]);
		if(digit >= '0' && digit <= '9')
			argb |= (digit - '0') << order;
		else if(digit >= 'a' && digit <= 'f')
			argb |= (digit - 'a' + 0x0A) << order;
	}

	a = (argb & 0xff000000) >> 24;
	r = (argb & 0xff0000) >> 16;
	g = (argb & 0xff00) >> 8;
	b = (argb & 0xff);
}

Tweakable::Colorf::Colorf(const std::string& val)
{
	Vector4d v(val);
	r = v.x;
	g = v.y;
	b = v.z;
	a = v.w;
}

Tweakable::Vector2d::Vector2d(const std::string& val)
{
	Vector4d v(val);
	x = v.x;
	y = v.y;
}

Tweakable::Vector3d::Vector3d(const std::string& val)
{
	Vector4d v(val);
	x = v.x;
	y = v.y;
	z = v.z;
}

Tweakable::Vector4d::Vector4d(const std::string& val)
{
	x = y = z = 0;
	float* out = &x;
	const char* start = val.c_str();
	const char* end = val.c_str() + val.size();
	char* tail = NULL;

	for(int i = 0; i < 4 && start < end; i++)
	{
		const char* stop = strchr(start, ',');
		if(stop == NULL)
			stop = end;

		std::string component(start, stop - start);
		*out = (float)strtod(component.c_str(), &tail);
		out++;
		start = stop + 1;
	}
}

void Tweakable::SetValue(Group& group, Type type, const std::string& name, const std::string& value)
{
	//printf("SetValue %s\n", attr["name"].c_str());
	std::map<std::string, Mapping>::iterator it = group.mMappings.find(name);
	if(it != group.mMappings.end())
	{
		GLF_ASSERT(it->second.mType == type);
		char* tail = NULL;
		switch(it->second.mType)
		{
			case TYPE_INT:
				*static_cast<int*>(it->second.mAddress) = atoi(value.c_str());
				break;

			case TYPE_FLOAT:
				*static_cast<float*>(it->second.mAddress) = (float)strtod(value.c_str(), &tail);
				break;

			case TYPE_STRING:
				*static_cast<std::string*>(it->second.mAddress) = value;
				break;

			case TYPE_BOOL:
				*static_cast<bool*>(it->second.mAddress) = (value == "true");
				break;

			case TYPE_COLOR:
				*static_cast<Color*>(it->second.mAddress) = Color(value);
				break;

			case TYPE_COLORF:
				*static_cast<Colorf*>(it->second.mAddress) = Colorf(value);
				break;

			case TYPE_VECTOR2D:
				*static_cast<Vector2d*>(it->second.mAddress) = Vector2d(value);
				break;

			case TYPE_VECTOR3D:
				*static_cast<Vector3d*>(it->second.mAddress) = Vector3d(value);
				break;

			case TYPE_VECTOR4D:
				*static_cast<Vector4d*>(it->second.mAddress) = Vector4d(value);
				break;
			case TYPE_UNKNOWN:
			default:
				break;
		}

		// keep name on stack in case ClearGroups() called in OnSetValue()
		std::string name = it->first;
		OnSetValue(name);
	}
}

void Tweakable::WriteGroup(Group& group, XmlWriter& writer, bool writeTag)
{
#if GLF_ENABLE_DEBUGGER
	if(writeTag)
	{
		StringPairVector attr;
		attr.push_back(StringPair("name", group.mName));
		writer.BeginTag("group", attr);
	}

	Color color;
	Colorf colorf;
	Vector2d v2d;
	Vector3d v3d;
	Vector4d v4d;
	std::string type;
	std::string value;
	char buffer[128];

	for(uint i = 0; i < group.mVariableNames.size(); i++)
	{
		std::map<std::string, Mapping>::const_iterator it = group.mMappings.find(group.mVariableNames[i]);
		if(it == group.mMappings.end())
		{
			continue;
		}

		switch(it->second.mType)
		{
			case TYPE_INT:
				type = "int";
				Snprintf_s(buffer, sizeof(buffer), "%d", *static_cast<int*>(it->second.mAddress));
				value = buffer;
				break;

			case TYPE_FLOAT:
				type = "float";
				Snprintf_s(buffer, sizeof(buffer), "%f", *static_cast<float*>(it->second.mAddress));
				value = buffer;
				break;

			case TYPE_STRING:
				type = "string";
				value = *static_cast<std::string*>(it->second.mAddress);
				break;

			case TYPE_BOOL:
				type = "bool";
				value = *static_cast<bool*>(it->second.mAddress) ? "true" : "false";
				break;

			case TYPE_COLOR:
				type = "color";
				color = *static_cast<Color*>(it->second.mAddress);
				Snprintf_s(buffer, sizeof(buffer), "%02x%02x%02x%02x", color.a, color.r, color.g, color.b);
				value = buffer;
				break;

			case TYPE_COLORF:
				type = "colorf";
				colorf = *static_cast<Colorf*>(it->second.mAddress);
				Snprintf_s(buffer, sizeof(buffer), "%f, %f, %f, %f", colorf.r, colorf.g, colorf.b, colorf.a);
				value = buffer;
				break;

			case TYPE_VECTOR2D:
				type = "vector2d";
				v2d = *static_cast<Vector2d*>(it->second.mAddress);
				Snprintf_s(buffer, sizeof(buffer), "%f, %f", v2d.x, v2d.y);
				value = buffer;
				break;

			case TYPE_VECTOR3D:
				type = "vector3d";
				v3d = *static_cast<Vector3d*>(it->second.mAddress);
				Snprintf_s(buffer, sizeof(buffer), "%f, %f, %f", v3d.x, v3d.y, v3d.z);
				value = buffer;
				break;

			case TYPE_VECTOR4D:
				type = "vector4d";
				v4d = *static_cast<Vector4d*>(it->second.mAddress);
				Snprintf_s(buffer, sizeof(buffer), "%f, %f, %f, %f", v4d.x, v4d.y, v4d.z, v4d.w);
				value = buffer;
				break;

			default:
				;
		}

		StringPairVector attr;
		attr.push_back(StringPair("name", group.mVariableNames[i]));
		attr.push_back(StringPair("value", value));
		attr.push_back(StringPair("range", it->second.mRange));
		attr.push_back(StringPair("desc", it->second.mDesc));

		writer.BeginTag(type, attr);
		writer.EndTag();
	}

	for(uint g = 0; g < group.mSubGroups.size(); g++)
	{
		WriteGroup(*group.mSubGroups[g], writer, true);
	}

	if(writeTag)
	{
		writer.EndTag();
	}
#endif // GLF_ENABLE_DEBUGGER
}

void Tweakable::SendValues()
{
#if GLF_ENABLE_DEBUGGER
	if(Tweakers::GetInstance()->GetDebugger())
	{
		Tweakers::GetInstance()->SendValues(this);
	}
#endif
}

#if GLF_ENABLE_DEBUGGER

Tweakers::Tweakers() :
	Module("TWEAKERS")
{
}

GLF_DEFINE_SINGLETON_WITH_DEP(Tweakers, Debugger)

Tweakers::~Tweakers()
{
}

//Tweakable* sTweak = NULL;
//Tweakable::Color col;
//float cam_far_value = 0;
//std::string myString;
//bool bTrue;
//bool bFalse;
//Tweakable::Vector3d vec3;
//
//void TestTweakers()
//{
//	Tweakable* t = sTweak;
//
//	if(sTweak == NULL)
//	{
//		sTweak = new Tweakable();
//		sTweak->RegisterVariable("col", col);
//		sTweak->RegisterVariable("cam_far_value", cam_far_value);
//		sTweak->RegisterVariable("vec3", vec3);
//		sTweak->RegisterVariable("myString", myString);
//		sTweak->RegisterVariable("bTrue", bTrue);
//		sTweak->RegisterVariable("bFalse", bFalse);
//		sTweak->LoadXML("test.xml");
//	}
//}

void Tweakers::UpdateFrame()
{
}

void Tweakers::Parse(int type, PacketReader& in)
{
	switch(type)
	{
		case MESSAGE_REQUEST_INSTANCES:
		{
			SendTweakables();
			break;
		}

		case MESSAGE_REQUEST_VALUES:
		{
			uint tweakableInstance = (uint)in.ReadIntLE();
			std::map<Tweakable*, bool>::iterator it = mTweakables.find((Tweakable*)tweakableInstance);
			if(it != mTweakables.end())
			{
				SendValues(it->first);
			}
			break;
		}

		case MESSAGE_SET_VALUE:
		{
			uint tweakableInstance = (uint)in.ReadIntLE();
			std::map<Tweakable*, bool>::iterator it = mTweakables.find((Tweakable*)tweakableInstance);
			if(it != mTweakables.end())
			{
				std::string xml((const char*)in.GetData(), in.Available());
				it->first->SetValuesFromXML(xml);

				if(it->first->mPersistent)
				{
					it->first->SavePersistent();
				}
			}
			break;
		}
	}
}

void Tweakers::ConnectionClosed()
{
	mRemovedTweakables.clear();
	mAddedTweakables.clear();
	mAddedTweakables = mTweakables;
}

void Tweakers::RegisterTweakable(Tweakable* tweakable)
{
	ScopeMutex mutex;
	mTweakables[tweakable] = true;
	mAddedTweakables[tweakable] = true;
}

void Tweakers::UnregisterTweakable(Tweakable* tweakable)
{
	ScopeMutex mutex;
	std::map<Tweakable*, bool>::iterator it = mTweakables.find(tweakable);
	if(it != mTweakables.end())
		mTweakables.erase(it);

	it = mAddedTweakables.find(tweakable);
	if(it != mAddedTweakables.end())
		mAddedTweakables.erase(it);

	if(Debugger::GetInstance()->IsConnected())
		mRemovedTweakables[tweakable] = true;
}

void Tweakers::SendTweakables()
{
	ScopeMutex mutex;
	// removed tweakables
	for(std::map<Tweakable*, bool>::const_iterator it = mRemovedTweakables.begin(); it != mRemovedTweakables.end(); ++it)
	{
		PacketWriter& pw = GetPacketWriter();
		pw.Init(MESSAGE_REMOVE_INSTANCE);
		pw.Write((uint)it->first);
		Send(pw);
	}
	mRemovedTweakables.clear();

	// added tweakables
	char buffer[128];
	for(std::map<Tweakable*, bool>::const_iterator it = mAddedTweakables.begin(); it != mAddedTweakables.end(); ++it)
	{
		const char* instanceName = it->first->GetInstanceName();
		if(instanceName == NULL || strlen(instanceName) == 0)
		{
			sprintf(buffer, "%s@0x%x", it->first->GetClassName(), (uint)it->first);
			instanceName = buffer;
		}

		PacketWriter& pw = GetPacketWriter();
		pw.Init(MESSAGE_ADD_INSTANCE);
		pw.Write((uint)it->first);
		pw.Write(instanceName);

		// serialize values
		std::string xmlNew;
		it->first->WriteValues(xmlNew);

		if(xmlNew.size() > 0)
			pw.Write(&xmlNew[0], xmlNew.size());

		Send(pw);
	}
	mAddedTweakables.clear();
}

void Tweakers::SendValues(Tweakable* tweakable)
{
	if(tweakable == NULL)
		return;

	PacketWriter& pw = GetPacketWriter();
	pw.Init(MESSAGE_VALUES);
	pw.Write((uint)tweakable);

	std::string xml;
	tweakable->WriteValues(xml);

	if(xml.size() > 0)
		pw.Write(&xml[0], xml.size());

	Send(pw);
}

#endif // GLF_ENABLE_DEBUGGER

} // end namespace gameswf
} // end namespace glf

